package com.coder4j.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.coder4j.starter.tools.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 请求参数过滤器，过滤掉参数中的空字符串及多余空格
 *
 * @author Keller
 * @date 2022-12-09 10:53:34
 */
@WebFilter(urlPatterns = "/*")
@Configuration
@Order(value = 1)
@Slf4j
public class RequestParamsFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String uri = request.getRequestURI();
        // 检查是否是WebSocket Upgrade请求
        if ("WebSocket".equalsIgnoreCase(request.getHeader("Upgrade"))) {
            // 如果是WebSocket Upgrade请求，则直接放行
            filterChain.doFilter(request, response);
            return;
        }

        log.info("开启请求参数过滤 {} ", uri);
        TrimRequestWrapper requestWrapper = new TrimRequestWrapper(request);
        filterChain.doFilter(requestWrapper, response);

    }

    @Slf4j
    public static class TrimRequestWrapper extends HttpServletRequestWrapper {
        /**
         * 保存处理后的参数
         */
        private Map<String, String[]> params = new HashMap<>();

        public TrimRequestWrapper(HttpServletRequest request) {
            //将request交给父类，以便于调用对应方法的时候，将其输出
            super(request);
            //对于非json请求的参数进行处理
            if (super.getHeader(HttpHeaders.CONTENT_TYPE) == null ||
                    (!super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) &&
                            !super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE))) {
                setParams(request);
            }
        }

        private void setParams(HttpServletRequest request) {
            //将请求的的参数转换为map集合
            Map<String, String[]> requestMap = request.getParameterMap();
            log.info("kv转化前参数：" + JSON.toJSONString(requestMap));
            this.params.putAll(requestMap);
            //去空操作
            this.modifyParameterValues();
            log.info("kv转化后参数：" + JSON.toJSONString(params));
        }

        /**
         * 将parameter的值去除空格后重写回去
         */
        public void modifyParameterValues() {
            Set<String> set = params.keySet();
            Iterator<String> it = set.iterator();
            while (it.hasNext()) {
                String key = it.next();
                String[] values = params.get(key);
                for (int i = 0; i < values.length; i++) {
                    if (StringUtils.isEmpty(values[i]) || "undefined".equals(values[i])) {
                        values[i] = null;
                    } else {
                        values[i] = values[i].trim();
                    }
                }
                params.put(key, values);
            }
        }

        /**
         * 重写getParameter 参数从当前类中的map获取
         */
        @Override
        public String getParameter(String name) {
            String[] values = params.get(name);
            if (values == null || values.length == 0) {
                return null;
            }
            return values[0];
        }

        /**
         * 重写getParameterValues
         */
        @Override
        public String[] getParameterValues(String name) {
            return params.get(name);
        }

        /**
         * 重写getInputStream方法  post类型的请求参数必须通过流才能获取到值
         * 这种获取的参数的方式针对于内容类型为文本类型，比如Content-Type:text/plain,application/json,text/html等
         * 在springmvc中可以使用@RequestBody 来获取 json数据类型
         * 其他文本类型不做处理，重点处理json数据格式
         * getInputStream() ，只有当方法为post请求，且参数为json格式是，会被默认调用
         */
        @Override
        public ServletInputStream getInputStream() throws IOException {
            //
            if (!super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) &&
                    !super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
                //如果参数不是json格式则直接返回
                return super.getInputStream();
            }
            //为空，直接返回
            String json = inputStreamToString(super.getInputStream(), "utf-8");
            if (StringUtils.isEmpty(json)) {
                return super.getInputStream();
            }
            log.info("json转化前参数：" + json);
            //json字符串首尾去空格
            JSONObject jsonObject = null;
            JSONArray jsonArray = null;
            try {
                jsonObject = filterObject(json);
            }catch (JSONException e){
                JSONArray array = JSONArray.parseArray(json);
                jsonArray = new JSONArray();
                for (Object item : array) {
                    JSONObject object = filterObject(JSON.toJSONString(item));
                    jsonArray.add(object);
                }
            }
            String filterJson = jsonObject == null ? jsonArray == null ? "" :jsonArray.toString() : jsonObject.toString();
            log.info("json转化后参数：" + filterJson);
            final ByteArrayInputStream bis = new ByteArrayInputStream(filterJson.getBytes("utf-8"));
            return new ServletInputStream() {

                @Override
                public int read() throws IOException {
                    return bis.read();
                }

                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {

                }
            };
        }
    }

    public static JSONObject filterObject(String json){
        JSONObject jsonObject = JSON.parseObject(json);
        JSONObject map = new JSONObject();
        for (String key : jsonObject.keySet()) {
            Object object = jsonObject.get(key);
            if (object instanceof String) {
                String value = (String) object;
                if (StringUtils.isEmpty(value)) {
                    continue;
                }
                map.put(key, value.trim());
            }else {
                map.put(key, object);
            }
        }
        return map;
    }

    public static String inputStreamToString(InputStream input, String encoding)
            throws IOException {
        StringBuilderWriter sw = new StringBuilderWriter();
        copy(input, sw, encoding);
        return sw.toString();
    }
    public static void copy(InputStream input, Writer output, String encoding)
            throws IOException {
        if (encoding == null) {
            copy(input, output);
        } else {
            InputStreamReader in = new InputStreamReader(input, encoding);
            copy(in, output);
        }
    }
    public static void copy(InputStream input, Writer output)
            throws IOException {
        InputStreamReader in = new InputStreamReader(input);
        copy(in, output);
    }
    public static int copy(Reader input, Writer output) throws IOException {
        long count = copyLarge(input, output);
        if (count > Integer.MAX_VALUE) {
            return -1;
        }
        return (int) count;
    }
    public static long copyLarge(Reader input, Writer output) throws IOException {
        return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);
    }
    public static long copyLarge(Reader input, Writer output, char [] buffer) throws IOException {
        long count = 0;
        int n = 0;
        while (EOF != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }
    private static final int EOF = -1;
    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
    public static class StringBuilderWriter extends Writer implements Serializable {

        private final StringBuilder builder;

        /**
         * Construct a new {@link StringBuilder} instance with default capacity.
         */
        public StringBuilderWriter() {
            this.builder = new StringBuilder();
        }

        /**
         * Construct a new {@link StringBuilder} instance with the specified capacity.
         *
         * @param capacity The initial capacity of the underlying {@link StringBuilder}
         */
        public StringBuilderWriter(int capacity) {
            this.builder = new StringBuilder(capacity);
        }

        /**
         * Construct a new instance with the specified {@link StringBuilder}.
         *
         * @param builder The String builder
         */
        public StringBuilderWriter(StringBuilder builder) {
            this.builder = builder != null ? builder : new StringBuilder();
        }

        /**
         * Append a single character to this Writer.
         *
         * @param value The character to append
         * @return This writer instance
         */
        @Override
        public Writer append(char value) {
            builder.append(value);
            return this;
        }

        /**
         * Append a character sequence to this Writer.
         *
         * @param value The character to append
         * @return This writer instance
         */
        @Override
        public Writer append(CharSequence value) {
            builder.append(value);
            return this;
        }

        /**
         * Append a portion of a character sequence to the {@link StringBuilder}.
         *
         * @param value The character to append
         * @param start The index of the first character
         * @param end The index of the last character + 1
         * @return This writer instance
         */
        @Override
        public Writer append(CharSequence value, int start, int end) {
            builder.append(value, start, end);
            return this;
        }

        /**
         * Closing this writer has no effect.
         */
        @Override
        public void close() {
        }

        /**
         * Flushing this writer has no effect.
         */
        @Override
        public void flush() {
        }


        /**
         * Write a String to the {@link StringBuilder}.
         *
         * @param value The value to write
         */
        @Override
        public void write(String value) {
            if (value != null) {
                builder.append(value);
            }
        }

        /**
         * Write a portion of a character array to the {@link StringBuilder}.
         *
         * @param value The value to write
         * @param offset The index of the first character
         * @param length The number of characters to write
         */
        @Override
        public void write(char[] value, int offset, int length) {
            if (value != null) {
                builder.append(value, offset, length);
            }
        }

        /**
         * Return the underlying builder.
         *
         * @return The underlying builder
         */
        public StringBuilder getBuilder() {
            return builder;
        }

        /**
         * Returns {@link StringBuilder#toString()}.
         *
         * @return The contents of the String builder.
         */
        @Override
        public String toString() {
            return builder.toString();
        }
    }
}
